﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using LayeredArchitectureSample.DomainModel;
using LayeredArchitectureSample.Infrastructure;

namespace LayeredArchitectureSample.DataAccess
{
    // TODO: autogenerate at some point
    /// <summary>
    /// </summary>
    /// <remarks> Use TransactionScope to get transactions across several repository operations</remarks>
    public class EntityFrameworkLocationRepository : ILocationRepository
    {
        private readonly IDatabaseContextFactory _dataContextFactory;

        internal EntityFrameworkLocationRepository(IDatabaseContextFactory dataContextFactory)
        {
            if (dataContextFactory == null) throw new ArgumentNullException("dataContextFactory");

            _dataContextFactory = dataContextFactory;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <exception cref="ArgumentException"></exception>
        /// <exception cref="CannotRemoveEntityException"></exception>
        /// <exception cref="EntityNotFoundException"></exception>
        public void RemoveLocation(long id)
        {
            try
            {
                using (LayeredSampleDataModel dataContext = _dataContextFactory.CreateDatabaseContext())
                {
                    Location locationWithGivenId = GetLocationWithGivenIdOrNull(id, dataContext);

                    if (locationWithGivenId == null)
                    {
                        throw new EntityNotFoundException(Resources.LocationNotFoundCannotRemoveIt.FormatWith(id));
                    }

                    dataContext.DeleteObject(locationWithGivenId);

                    dataContext.SaveChanges();
                }
            }
            catch (UpdateException exception)
            {
                SqlException innerExceptionAsSqlException = exception.InnerException as SqlException;

                if (innerExceptionAsSqlException != null &&
                    innerExceptionAsSqlException.Number == Constants.Database.ReferencesToObjectDetectedErrorCode)
                {
                    throw new CannotRemoveEntityException(Resources.FailedToRemoveLocationUsedSomewhere.FormatWith(id), exception);
                }

                throw new CannotRemoveEntityException(Resources.FailedToRemoveLocation.FormatWith(id), exception);
            }
            catch (EntityException exception)
            {
                throw new CannotRemoveEntityException(Resources.FailedToRemoveLocation.FormatWith(id), exception);
            }
            catch (InvalidOperationException)
            {
                throw new CannotRemoveEntityException(Resources.DuplicateLocationsDetected.FormatWith(id));
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        private Location GetLocationWithGivenIdOrNull(long id, LayeredSampleDataModel context)
        {
            return (from Location location in context.Locations
                    where location.Id == id
                    select location).SingleOrDefault();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentException"></exception>
        /// <exception cref="CannotAddEntityException"></exception>
        public DomainModel.Location AddNewLocation(string name)
        {
            // Validation
            var newLocationToBe = new DomainModel.Location(1, name);

            using (LayeredSampleDataModel dataContext = _dataContextFactory.CreateDatabaseContext())
            {
                var newLocation = new Location() {Name = name};

                dataContext.Locations.AddObject(newLocation);

                try
                {
                    dataContext.SaveChanges();
                }
                catch (UpdateException exception)
                {
                    SqlException innerException = exception.InnerException as SqlException;

                    if (innerException != null &&
                        innerException.Number == Constants.Database.UniqueConstraintViolationErrorCode)
                    {
                        throw new CannotAddEntityException(Resources.LocationAlreadyExists.FormatWith(name));
                    }
                    
                    throw new CannotAddEntityException(Resources.CannotAddLocation, exception);
                }

                return new DomainModel.Location(newLocation.Id, newLocation.Name);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        /// <exception cref="EntityRetrievalException"></exception>
        public IEnumerable<DomainModel.Location> GetAll()
        {
            using (LayeredSampleDataModel dataContext = _dataContextFactory.CreateDatabaseContext())
            {
                try
                {
                    var locations = new List<DomainModel.Location>();

                    foreach (Location location in dataContext.Locations)
                    {
                        locations.Add(new DomainModel.Location(location.Id, location.Name));
                    }

                    return locations;
                }
                catch (EntityException exception)
                {
                    throw new EntityRetrievalException(Resources.FailedToRetrieveLocations, exception);
                }
            }
        }
    }
}